Skip to content

S03-03 JS-基础-对象

[TOC]

面向对象

对象

语法特性

对象(Object):是一种复合数据类型,用于存储键值对(key-value pairs)的集合。


为什么需要对象类型

基本数据类型可以存储一些简单的值,但是现实世界的事物抽象成程序时,往往比较复杂:

  • 比如一个人,有自己的特性(比如姓名、年龄、身高),有一些行为(比如跑步、学习、工作)。
  • 比如一辆车,有自己的特性(比如颜色、重量、速度),有一些行为(比如行驶)。

这个时候,我们需要一种新的类型将这些特性和行为组织在一起,这种类型就是对象类型。


核心特性

  1. 键值对结构

    对象由 属性(property) 组成,每个属性包含:

    • 键(Key): 字符串或 Symbol 类型(唯一标识符)。
    • 值(Value): 任意数据类型(字符串、数字、函数、数组,甚至其他对象)。

    属性之间是以逗号( comma )分割

    js
    let user = {
      name: "Alice",      // 键: "name",  值: "Alice"
      age: 30,            // 键: "age",   值: 30
      isAdmin: true,      // 键: "isAdmin", 值: true
      sayHello: function() { console.log("Hello!") } // 键: "sayHello", 值: 函数
    };
  2. 动态性:可随时添加/删除属性

    js
    user.email = "alice@example.com"; // 添加新属性
    delete user.isAdmin;               // 删除属性
  3. 引用类型

    对象是引用类型。赋值时传递的是内存地址(而非值副本):

    js
    let obj1 = { a: 1 };
    let obj2 = obj1;      // obj2 和 obj1 指向同一对象,传递的是内存地址
    obj2.a = 2;           // 修改 obj2 会影响 obj1
    console.log(obj1.a);  // 输出 2
  4. 方法(Method)

    当值为函数时,该属性称为对象的方法

    js
    user.sayHello(); // 调用对象方法 → 输出 "Hello!"
  5. 原型链(Prototype)

    对象通过原型链实现继承。每个对象都有一个隐藏属性 [[Prototype]](可通过 __proto__Object.getPrototypeOf() 访问)。

创建对象

创建对象的常用方式:

对象的创建方法有很多,包括三种:

  1. 对象字面量:最常用
  2. new Object()
  3. new 构造函数:用于创建多个相似对象
  4. Object.create():可以指定原型

对象字面量

最常用,直接使用 {} 语法创建对象,适合创建单个对象。

js
const person = {
  name: "张三",
  age: 30,
  greet() {
    return `你好,我是${this.name}`;
  }
};
console.log(person.greet()); // "你好,我是张三"

new Object()

使用内置的 Object 构造函数创建

js
const car = new Object();
car.brand = "Toyota";
car.model = "Camry";
car.drive = function() {
  return `驾驶${this.brand} ${this.model}`;
};

new 构造函数

使用 new 关键字和自定义构造函数

js
function Product(name, price) {
  this.name = name;
  this.price = price;
  this.getInfo = function() {
    return `${this.name}: ¥${this.price}`;
  };
}

const product1 = new Product("手机", 2999);
const product2 = new Product("耳机", 599);

Object.create()

基于现有对象创建新对象,可以指定原型

js
const personProto = {
  greet() {
    return `你好,我是${this.name}`;
  }
};

const john = Object.create(personProto);
john.name = "John";
john.age = 28;

// 创建带属性的对象
const mary = Object.create(personProto, {
  name: { value: "Mary" },
  age: { value: 32 }
});

操作对象

属性访问
  • 点表示法.,用于已知且有效的变量标识符

    js
    const person = { name: "Alice" };
    console.log(person.name); // "Alice"
  • 方括号表示法[],用于动态属性名或包含特殊字符的属性

    js
    console.log(person["name"]); // "Alice"
    1. 键名格式:键名必须放在引号里面,否则会被当作变量处理。

      js
      const key = "age";
      console.log(person["name"]); // "Alice"
      console.log(person[key]);    // 30 (使用变量)
      
      // 特殊属性名
      person["home address"] = "Beijing";
    2. 使用表达式:方括号运算符内部还可以使用表达式。

      js
      obj['hello' + ' world']
      obj[3 + 3]
属性添加/修改

属性添加:JS 允许属性的“后绑定”,也就是说,你可以在任意时刻新增属性,没必要在定义对象的时候,就定义好属性。

js
var obj = {};

// 添加新属性
obj.foo = 'Hello'; // 点运算符
obj['bar'] = 'World'; // 方括号运算符

属性修改:语法和属性添加类似,添加一个已存在的属性,就会修改该属性的值。

js
const car = { brand: "Toyota" };

// 修改现有属性
car.brand = "Honda";
属性删除 delete

delete命令:用于删除对象的属性:删除成功后返回true

js
var obj = { p: 1 };

delete obj.p // true
obj.p // undefined

删除一个不存在的属性delete不报错,而且返回true

js
var obj = {};
delete obj.p // true

只有一种情况,delete命令会返回false:那就是该属性存在,且不得删除。

js
var obj = Object.defineProperty({}, 'p', {
  value: 123,
  configurable: false
});

obj.p // 123
delete obj.p // false

delete命令只能删除对象本身的属性,无法删除继承的属性

js
var obj = {};
delete obj.toString // true
obj.toString // function toString() { [native code] }
属性枚举

查看一个对象本身的所有属性,可以使用Object.keys方法。

Object.keys()(obj),用于获取对象的所有可枚举属性名

js
var obj = {
  key1: 1,
  key2: 2
};

Object.keys(obj); // ['key1', 'key2']
属性遍历 for...in

for...in循环:用来遍历一个对象的全部属性。

js
var obj = {a: 1, b: 2, c: 3};

for (var i in obj) {
  console.log('键名:', i);
  console.log('键值:', obj[i]);
}
// 键名: a 键值: 1
// 键名: b 键值: 2
// 键名: c 键值: 3

for...in循环有两个使用注意点

  • 只遍历可遍历属性:它遍历的是对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性。

    js
    var obj = {};
    
    // toString 属性是存在的
    obj.toString // toString() { [native code] }
    
    for (var p in obj) {
      console.log(p);
    } // 没有任何输出
  • 它不仅遍历对象自身的属性,还遍历继承的属性:一般需求都是只遍历自身的属性,所以需要使用hasOwnProperty()方法过滤掉继承的属性

    js
    var person = { name: '老张' };
    
    for (var key in person) {
      if (person.hasOwnProperty(key)) {
        console.log(key); // name
      }
    }

for 循环:还可以使用 for 循环来遍历对象的属性。

image-20250519104845994

属性是否存在 in

in运算符:用于检查对象是否包含某个属性(注意,检查的是键名,不是键值),如果包含就返回true,否则返回false

js
var obj = { p: 1 };

'p' in obj // true

识别继承属性

  • in运算符不能识别哪些属性是对象自身的,哪些属性是继承的。

    js
    var obj = { p: 1 };
    
    'p' in obj // true
    'toString' in obj // true
  • 可以使用对象的hasOwnProperty()方法判断一下,是否为对象自身的属性。

    js
    var obj = {};
    if ('toString' in obj) {
      console.log(obj.hasOwnProperty('toString')) // false
    }

栈内存/堆内存

我们知道程序是需要加载到内存中来执行的,我们可以将内存划分为两个区域:栈内存堆内存

  • 原始类型:占据的空间是在栈内存中分配的;
  • 对象类型:占据的空间是在堆内存中分配的;

image-20250519104919183

后续我们会学习图中的其他知识

目前我们先掌握堆和栈的概念即可

值类型/引用类型【

值类型

值类型(Primitive Types,原始类型):是不可变的数据类型,它们直接存储在变量访问的位置(栈),当你操作它们时,你操作的是实际的原始值。

image-20250627164805593


按值传递:传递的是值的副本。

js
let a = 10;
let b = a; // b 获取的是 a 的值的副本
a = 20;
console.log(a); // 20
console.log(b); // 10(不受 a 变化影响)

存储/比较的都是原始值本身:而不是指向内存中位置的引用

js
console.log(5 === 5); // true(值相同)
console.log('hi' === 'hi'); // true

引用类型

引用类型(对象类型):是指那些值存储在堆内存中,而变量保存的是内存地址(引用) 的数据类型。当你操作引用类型时,实际上是在操作指向实际数据存储位置的指针,而不是直接操作数据本身。

js
// 引用类型(对象类型)
let b = { value: 20 }; // 变量 b 存储的是内存地址,指向实际对象

image-20250627164821964


变量保存的是内存地址(引用):实际值存储在堆内存中,在变量中保存的是对象的“引用”。

js
let obj1 = { id: 1 };
let obj2 = obj1; // obj2 获得的是 obj1 的引用(内存地址)

obj2.id = 2;     // 修改 obj2 会影响 obj1
console.log(obj1.id); // 输出 2

值类型 vs 引用类型

比较两个值/对象

js
// 比较两个值
let m = 123
let n = 123
console.log(m === n) // true


// 比较两个对象
let a = {}
let b = {}
console.log(a === b) // false

函数参数传递

  • 值类型参数:传递值的副本

    js
    function changeValue(num) {
      num = 100;
    }
    
    let original = 50;
    changeValue(original);
    console.log(original); // 50 (未改变)
  • 引用类型参数:传递引用的副本(仍指向同一对象)

    js
    function updateProfile(user) {
      user.age = 30;
    }
    
    let person = { name: "Alice" };
    updateProfile(person);
    console.log(person); // {name: "Alice", age: 30} (已修改)

this

为什么需要this

其他语言中的this

在常见的编程语言中,几乎都有this这个关键字(Objective-C中使用的是self),但是JavaScript中的this和常见的面向对象语言中的this不太一样:

常见面向对象的编程语言中,比如Java、C++、Swift、Dart等等一系列语言中,this通常只会出现在类的方法中

也就是你需要有一个类,类中的方法(特别是实例方法)中,this代表的是当前调用对象;


JS中的this

但是JS中的this更加灵活,无论是它出现的位置还是它代表的含义;


有this和没有this的区别

我们来看一下编写一个obj的对象,有this和没有this的区别:

image-20250519105038144

image-20250519105044227

this指向什么

this总是指向一个对象

js
// 在全局中,this指向window对象
this // window

// 在函数中,this指向调用它的对象
function fn() {
    console.log(this) 
}
var obj = {}
obj.fn = fn
obj.fn() // obj

this就是属性或方法“当前”所在的对象

js
var person = {
  name: '张三',
  describe: function () {
    return '姓名:'+ this.name; // this指向当前的person对象
  }
};

person.describe()// "姓名:张三"

this的指向是可变的

由于对象的属性可以赋给另一个对象,所以属性所在的当前对象是可变的,这会导致this也是可变的。

js
var A = {
  name: '张三',
  describe: function () {
    return '姓名:'+ this.name;
  }
};

var B = {
  name: '李四'
};

B.describe = A.describe;
B.describe() // "姓名:李四"

this的本质【

函数在内存中的保存方式

类和对象的思维方式

我们来思考一个问题:如果需要在开发中创建一系列的相似的对象,我们应该如何操作呢?

比如下面的例子:

  • 游戏中创建一系列的英雄(英雄具备的特性是相似的,比如都有名字、技能、价格,但是具体的值又不相同)
  • 学生系统中创建一系列的学生(学生都有学号、姓名、年龄等,但是具体的值又不相同)

当然,一种办法是我们创建一系列的对象:

image-20250519105146255

这种方式有一个很大的弊端:创建同样的对象时,需要编写重复的代码;

  • 我们是否有可以批量创建对象,但是又让它们的属性不一样呢?

创建对象的方案 – 工厂函数

我们可以想到的一种创建对象的方式:工厂函数

  • 我们可以封装一个函数,这个函数用于帮助我们创建一个对象,我们只需要重复调用这个函数即可;
  • 工厂模式其实是一种常见的设计模式;

image-20250519105157127

image-20250519105207009

认识构造函数

工厂方法创建对象有一个比较大的问题:我们在打印对象时,对象的类型都是Object类型

  • 但是从某些角度来说,这些对象应该有一个他们共同的类型;
  • 下面我们来看一下另外一种模式:构造函数的方式;

我们先理解什么是构造函数?

  • 构造函数也称之为构造器(constructor),通常是我们在创建对象时会调用的函数;
  • 在其他面向的编程语言里面,构造函数是存在于类中的一个方法,称之为构造方法;
  • 但是JavaScript中的构造函数有点不太一样,构造函数扮演了其他语言中类的角色;

也就是在JavaScript中,构造函数其实就是类的扮演者:

  • 比如系统默认给我们提供的Date就是一个构造函数,也可以看成是一个类;
  • 在ES5之前,我们都是通过function来声明一个构造函数(类)的,之后通过new关键字来对其进行调用;
  • 在ES6之后,JavaScript可以像别的语言一样,通过class来声明一个类;

那么类和对象到底是什么关系呢?

类和对象的关系

那么什么是类(构造函数)呢?

  • 现实生活中往往是根据一份描述/一个模板来创建一个实体对象的.
  • 编程语言也是一样, 也必须先有一份描述, 在这份描述中说明将来创建出来的对象有哪些属性(成员变量)和行为(成员方法)

比如现实生活中,我们会如此来描述一些事物:

  • 比如水果fruits是一类事物的统称,苹果、橘子、葡萄等是具体的对象;
  • 比如人person是一类事物的统称,而Jim、Lucy、Lily、李雷、韩梅梅是具体的对象;

image-20250519105226750

image-20250519105239975

比如植物大战僵尸游戏

image-20250519105252513

JavaScript中的类(ES5)

我们前面说过,在JavaScript中类的表示形式就是构造函数。

JavaScript中的构造函数是怎么样的?

  • 构造函数也是一个普通的函数,从表现形式来说,和千千万万个普通的函数没有任何区别;
  • 那么如果这么一个普通的函数被使用new操作符来调用了,那么这个函数就称之为是一个构造函数;

如果一个函数被使用new操作符调用了,那么它会执行如下操作:

    1. 在内存中创建一个新的对象(空对象);
    1. 这个对象内部的 [[prototype]] 属性会被赋值为该构造函数的prototype属性;(后面详细讲);
    1. 构造函数内部的this,会指向创建出来的新对象;
    1. 执行函数的内部代码(函数体代码);
    1. 如果构造函数没有返回非空对象,则返回创建出来的新对象;

接下来,我们可以用构造函数的方式来实现一下批量创建学生。

创建对象的方案 – 构造函数(类)

我们来通过构造函数实现一下:

image-20250519105305407

这个构造函数可以确保我们的对象是有Person的类型的(实际是constructor的属性,这个我们后续再探讨);

事实上构造函数还有很多其他的特性:

  • 比如原型、原型链、实现继承的方案
  • 比如ES6中类、继承的实现;

在 JavaScript 中,对象(Object) 是一种复合数据类型,用于存储键值对(key-value pairs)的集合。它是 JavaScript 最核心的概念之一,几乎所有元素都可以被视为对象(如函数、数组、日期等)。


核心特性:

  1. 键值对结构 对象由 属性(property) 组成,每个属性包含:

    • 键(Key): 字符串或 Symbol 类型(唯一标识符)。
    • 值(Value): 任意数据类型(字符串、数字、函数、数组,甚至其他对象)。
    js
    let user = {
      name: "Alice",      // 键: "name",  值: "Alice"
      age: 30,            // 键: "age",   值: 30
      isAdmin: true,      // 键: "isAdmin", 值: true
      sayHello: function() { console.log("Hello!") } // 键: "sayHello", 值: 函数
    };
  2. 添加/删除属性

    js
    user.email = "alice@example.com"; // 添加新属性
    delete user.isAdmin;               // 删除属性
  3. 引用类型

    对象是引用类型。赋值时传递的是内存地址(而非值副本):

    js
    let obj1 = { a: 1 };
    let obj2 = obj1;      // obj2 和 obj1 指向同一对象
    obj2.a = 2;           // 修改 obj2 会影响 obj1
    console.log(obj1.a);  // 输出 2
  4. 方法(Method)

    当值为函数时,该属性称为对象的方法

    js
    user.sayHello(); // 调用对象方法 → 输出 "Hello!"
  5. 原型链(Prototype)

    对象通过原型链实现继承。每个对象都有一个隐藏属性 [[Prototype]](可通过 __proto__Object.getPrototypeOf() 访问)。


创建对象的常用方式:

  1. 字面量语法(最常用)

    js
    let person = { 
      name: "Bob", 
      greet() { console.log("Hi!") } 
    };
  2. new Object() 构造函数

    js
    let car = new Object();
    car.brand = "Toyota";
  3. 构造函数(用于创建多个相似对象)

    js
    function Person(name) {
      this.name = name;
    }
    let alice = new Person("Alice");
  4. Object.create()(指定原型)

    js
    let prototype = { log: () => console.log("Prototype") };
    let obj = Object.create(prototype); // obj 继承 prototype

操作对象:

操作示例
访问属性user.nameuser["name"]
添加属性user.city = "Paris"
删除属性delete user.age
检查属性存在"name" in useruser.hasOwnProperty("name")
遍历属性for (let key in user) { console.log(key) }

内置对象方法:

  • Object.keys(obj):返回所有可枚举属性的键数组。
  • Object.values(obj):返回属性值数组。
  • Object.entries(obj):返回键值对数组(如 [ ["name", "Alice"], ["age", 30] ])。
  • Object.assign(target, src):合并对象(浅拷贝)。

特殊对象类型:

类型说明
数组有序集合,键为数字索引。let arr = [1, 2, 3]
函数可执行对象,含 call()/apply() 等方法。
内置对象Date, Math, JSON, RegExp 等。